/* strerror_tsd.c

   An implementation of strerror() that is made thread-safe through
   the use of thread-specific data.

   See also strerror_tls.c.
*/
#define _GNU_SOURCE /* Get '_sys_nerr' and '_sys_errlist' \
                       declarations from <stdio.h> */
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* Get declaration of strerror() */

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;

/* Maximum length of string in per-thread buffer returned by strerror() */
#define MAX_ERROR_LEN 256

/* Free thread-specific data buffer */
static void destructor(void *buf) { free(buf); }

static void errExitEN(int errn, char *msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errn));
  exit(EXIT_FAILURE);
}

static void errExit(char *msg) {
  perror(msg);
  exit(EXIT_FAILURE);
}

/* One-time key creation function */
static void createKey(void) {
  int s;

  /* Allocate a unique thread-specific data key and save the address
     of the destructor for thread-specific data buffers */
  s = pthread_key_create(&strerrorKey, destructor);
  if (s != 0) errExitEN(s, "pthread_key_create");
}

char *strerror(int err) {
  int s;
  char *buf;

  /* Make first caller allocate key for thread-specific data */
  s = pthread_once(&once, createKey);
  if (s != 0) errExitEN(s, "pthread_once");

  /* If first call from this thread, allocate
      buffer for thread, and save its location */
  buf = pthread_getspecific(strerrorKey);
  if (buf == NULL) {
    buf = malloc(MAX_ERROR_LEN);
    if (buf == NULL) errExit("malloc");

    s = pthread_setspecific(strerrorKey, buf);
    if (s != 0) errExitEN(s, "pthread_setspecific");
  }

  if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
    snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
  } else {
    strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
    buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
  }

  return buf;
}